package jass.generators;
import jass.engine.*;
import java.io.*;

/** Map HSB color to [pitch reson-width loudness]
    Represent a color (h,s,b) by a noise source of loudness b,
    filtered through a reson bank with Shepard frequencies
    (i.e. octaves apart covering the audible range) and some damping d = 1*freq/freq_lowest.
    The hue h [0 1] will be mapped to an octave range in freq. (Note the dampings
    are also scaled when freq. is scaled to preserve scale invariance of octaves.) The saturation
    s [0 1] will be mapped to the "material" (i.e., the width of the resonances will be
    multiplied by a factor depending on the saturation.
    @author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class ColorSonificator extends ModalObjectWithOneContactInterpolated {
//public class ColorSonificator extends ModalObjectWithOneContact {

    private float lowestFrequency = 10;     // lower limit of hearing
    private float minimumDamping = 3;       // for completely saturated color
    private float maximumDamping = 25;   // for completely unsaturated color
    // envelope unused:
    private float dBMax = 60;              // level difference between black and white
    private double fudgePower = .35; // in theory energy in mode (f,d,a) is a^2/d, so if we scale a by
    // d^.5 enery damping independent. But does not sound so because of critical band effect so fudge power 
             

    /** Create and initialize.
        @param srate sampling rate in Hertz.
        @param bufferSize Buffer size used for real-time rendering.
    */
    public ColorSonificator(float srate,int bufferSize) {
        super(bufferSize);
        this.srate = srate;
        int np = 1; // 1 location
        // f=lowestFrequency*2^n, n = 0,...nf-1
        int nf = (int)(Math.floor(Math.log((srate/2)/lowestFrequency)/Math.log(2)));
        this.modalModel = new ModalModel(nf,np);
        allocate(nf,np);
        this.tmpBuf = new float[bufferSize];
        createModalModel();
        // only if deriving from ModalObjectWithOneContactInterpolated
        super.allocate_new(nf,np);
    }

    private final static double minDB = 1.e-10;

    /** Convert from level to decibel
        @param a level
        @return decibels = 20Log_10(a)
    */
    public static double decibel(double a) {
        double y;
        double abs_a = Math.abs(a);
        if(abs_a < minDB) {
            y = minDB;
        } else {
            y = abs_a;
        }
        return 20*Math.log(abs_a)/Math.log(10);
    }

    /**
       Set power in scaling law for gains a = a*d^fudgePower. .5 for constant energy but lower
       in practice because of critical band effect, to have d-independent loudness
       @param p fudgePower
    */
    public void setFudgePower(float p) {
        fudgePower = (double)p;
    }

    /**
       Get power in scaling law for gains a = a*d^fudgePower. .5 for constant energy but lower
       in practice because of critical band effect, to have d-independent loudness
       @return fudgePower
    */
    public float getFudgePower() {
        return (float)fudgePower;
    }
    
    protected void createModalModel() {
        for(int i=0;i<modalModel.nf;i++) {
            modalModel.d[i] = 1;
            modalModel.f[i] = lowestFrequency * (float)Math.pow(2.,(double)i);
            modalModel.d[i] = modalModel.f[i]/lowestFrequency;
            modalModel.a[0][i] = 1;
            //System.out.println("f,a="+modalModel.f[i]+","+modalModel.a[0][i]);
        }
    }

    /** Set damping range corresponding to saturation
        @param dmin damping for saturated color
        @param dmax damping for unsaturated color
    */
    public void setSaturationLimits(float dmin,float dmax) {
        this.minimumDamping = dmin;
        this.maximumDamping = dmax;
    }

    /** Set level difference between white and black
        @param dbmax maximum level difference between white and black
    */
    public void setMaximumLevelDifference(float dbmax) {
        this.dBMax = dbmax;
    }
    
    /** Get level difference between white and black
        @return maximum level difference between white and black
    */
    public float getMaximumLevelDifference() {
        return this.dBMax;
    }

    private float[] satLimits = new float[2];

    /** Get damping range corresponding to saturation
        @return [minimumDamping maximumDamping]
    */
    public float[] getSaturationLimits() {
        satLimits[0] = minimumDamping;
        satLimits[1] = maximumDamping;
        return satLimits;
    }

    /** Set hue, saturation, brightness and slide velocity (1 = max)
        @param h hue in range 0-1
        @param s saturation in range 0-1
        @param b brightness in range 0-1
        @param v velocity in range 0-1
    */
    public void setHSB_V(float h,float s,float b, float v) {
        float dscale =  (float)(maximumDamping*Math.exp(-s*Math.log(maximumDamping/minimumDamping)));
        float fscale = (float)(v * Math.pow(2.,(double)h));
        // to keep level damping independent:
        double bcorrected = b*Math.pow((double)(dscale/maximumDamping),fudgePower); 
        //float ascale = (float)(Math.exp(Math.log(10)*dBMax*(bcorrected-1)/20));
        float ascale = (float)Math.pow((double)(dscale*fscale),fudgePower);
        setDamping(fscale*dscale/v); // damping not affected by speed
        setFrequencyScale(fscale);
        //System.out.println(fscale);
        //ascale = 1;
        setGain(ascale);
    }

    
    /** Set hue, saturation and brightness
        @param h hue in range 0-1
        @param s saturation in range 0-1
        @param b brightness in range 0-1
    */
    public void setHSB(float h,float s,float b) {
        float velocity = 1;
        setHSB_V(h,s,b,velocity);
    }

}
